;; ==========
;; TCP CLIENT
;; ==========
;; This TCP client connects to a server and sends it a message.
(define-module (tcp-client)
  #:export (run-client
            display-message-handler
            display-message-protocol
            make-client-protocol))

(use-modules (ice-9 binary-ports)
             (ice-9 textual-ports)
             (rnrs bytevectors)
             (networking-lib helpers)
             (json))

#;(define receive-buffer (make-bytevector 1024))

(define (connect-to-server address port)
  (let ([sock
         ;; create a TCP socket
         (socket PF_INET SOCK_STREAM
                 (protoent:proto (getprotobyname "TCP")))])
    ;; connect to somewhere
    ;; CONNECT: ARGUMENTS:
    ;; 1. the socket to use
    ;; 2. the protocol family
    ;; 3. the address to connect to
    ;; 4. the port on which to connect
    (connect sock
             (make-socket-address AF_INET
                                  address
                                  12345))
    sock))

(define (handle-server-messages in-out-sock protocol)
  (display "INFO: now handling messages from server according to protocol\n")
  (protocol in-out-sock))

(define (display-message-handler in-out-sock message)
  (display (simple-format #f "RECEIVED: ~s\n" message)))

#;(define (close-socket in-out-sock)
  (display (simple-format #f "~s\n" "EOF received. Closing socket ..."))
  (close in-out-sock)
  (display (simple-format #f "~s\n" "Socket closed.")))

(define* (run-client port #:key (protocol display-message-protocol))
  (define in-out-sock (connect-to-server INADDR_LOOPBACK port))
  ;; send greeting
  #;(put-string in-out-sock "Hello from client!\n")
  ;; react on messages inside another thread to keep responsive
  (call-with-new-thread
   (λ ()
     (handle-server-messages in-out-sock protocol)))
  ;; return in-out-sock to be able to close it from REPL
  in-out-sock)

(define* (make-client-protocol
          #:key
          (port-reader get-line)
          (message-handler display-message-handler)
          (eof-handler close))
  "A client protocol is initialized with an input output socket. "
  "The socket cannot be specified ahead of time, since it is created by the client, "
  "when the client connects to the server and not earlier. "
  "It the client protocol will handle messages from the socket according to its implementation."
  (λ (in-out-sock)
    ;; Handle infinitely many messages.
    (while (not (port-closed? in-out-sock))
      ;; Receiving a message is blocking.
      ;; It should not lead to a high CPU usage.
      (let ([received-data (port-reader in-out-sock)])
        (cond [(eof-object? received-data)
               (eof-handler in-out-sock)
               (break)]
              [else
               (message-handler in-out-sock received-data)])))))

(define display-message-protocol (make-client-protocol))
